from plyny.collections2.slice import convert_args, countedslice
from plyny.core.itertools2 import listiter

from collections import deque
from itertools import tee, islice, chain
        

class listwrapper(object):
    """ Non destructively keep current list position. """
    def __init__(self, values):
        self._values = values
        self._cur = 0

    def __iter__(self):
        for v in listiter(self._values, self._cur, len(self._values)):
            self._cur += 1
            yield v

    def extend(self, values):
        self._values.extend(values)

    def __len__(self):
        return len(self._values)


class bigchain(object):
    def __init__(self, *itr):
        self._current_list = self._values = self._length = None
        self.clear()
        self._init()

        for i in itr:
            self.extend(i)

    def __len__(self):
        if self._length is not None:
            return self._length
        raise TypeError()

    def _init(self):
        pass

    def clear(self):
        self._current_list = None
        self._values = deque()
        self._length = 0

    def copy(self):
        c = self.__class__(self._copy())
        c._length = self._length
        return c

    def _copy(self):
        values = tee(chain(*self._values))
        self._values = deque([values[0]])
        self._current_list = None
        return values[1]

    def __getslice__(self, *slcargs):
        return self.slice(*slcargs)

    def slice(self, *slcargs):
        try:
            length = len(self)
        except KeyError:
            length = None

        return self.__class__(countedslice(self._copy(),
            *convert_args(*slcargs, length=length)))

    def is_empty(self):
        if len(self._values) == 0:
            # easy enough
            return True

        # now the problem is that an empty iterator could have been pushed on
        # to this chain, with nothing else. since we check lazily we will not
        # discover until peek at which point it is too late

        if self._current_list is not None and len(self._current_list) > 0:
            return False

        # need to check all the values until we find something

        for i in self._values:
            if isinstance(i, list) and len(i) > 0:
                return False

        # everything is an iterator. we have no choice but to peek, which will
        # fail if every itr is empty

        try:
            self._peek()
        except ValueError:
            return True

    def append(self, value):
        if self._current_list is None:
            self._current_list = []
            self._values.append(self._current_list)

        self._current_list.append(value)
        self._inc_length(1)

    def extend(self, itr):
        if itr is self:
            raise ValueError('Cannot add self')

        try:
            l = len(itr)
        except TypeError:
            l = None

        if l is not None:
            self._inc_length(l)
        else:
            self._length = None

        if isinstance(itr, list):
            if self._current_list is not None:
                self._current_list.extend(itr)
                return

            i = listwrapper(itr)
            self._current_list = i
        else:
            i = iter(itr)
            self._current_list = None

        self._values.append(i)

    def __iadd__(self, other):
        self.extend(other)
        return self

    def __add__(self, other):
        return self.__class__(self.copy(), other)

    def peek(self):
        if self.is_empty():
            raise ValueError('No values')

        return self._peek()

    def _peek(self):
        if isinstance(self._values[0], list) and len(self._values[0]) > 0:
            return self._values[0][0]
        else:
            self._current_list = None
            value = list(islice(self._values[0], 0, 1))
            if len(value) == 0:
                # empty list, remove
                self._values.popleft()
                if len(self._values) == 0:
                    raise ValueError('empty')
                return self._peek()

            value = value[0]
            self._values.appendleft([value])
            return value

    def _inc_length(self, by):
        if self._length is not None:
            self._length += by

    def __iter__(self):
        while len(self._values) > 0:
            self._current_list = None

            left = self._values[0]

            is_list = isinstance(left, list)
            if is_list:
                while left:
                    v = left.pop(0)
                    self._on_inner_itr(v)
                    yield v
            else:
                for vi in left:
                    self._on_inner_itr(vi)
                    yield vi

            self._values.popleft()

    def _on_inner_itr(self, value):
        self._inc_length(-1)


class retaining_bigchain(bigchain):
    def _init(self):
        self._stored = []

    def __len__(self):
        if self._length is not None:
            return self._length

        list(self._unroll())
        return len(self._stored)

    def _unroll(self):
        return super(retaining_bigchain, self).__iter__()

    def is_empty(self):
        return len(self._stored) == 0 and super(retaining_bigchain,
                self).is_empty()

    def peek(self):
        if len(self._stored) > 0:
            return self._stored[0]

        return super(retaining_bigchain, self).peek()

    def __iter__(self):
        return chain(iter(self._stored), self._unroll())

    def _on_inner_itr(self, value):
        self._stored.append(value)

    def __getitem__(self, index):
        if index < 0:
            raise ValueError('%d < 0' % index)

        if index < len(self._stored):
            return self._stored[index]

        list(iter(self))
        return self._stored[index]

    def _copy(self):
        return list(self)
